package edu.arizona.sista.learn.model.holding;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import edu.arizona.sista.learn.model.Interval;
import edu.arizona.sista.learn.model.motion.MovementEvent;
import edu.arizona.sista.learn.model.motion.RectilinearTrajectory;
import edu.arizona.sista.learn.model.displaying.DisplayingSomething;
import edu.arizona.sista.learn.model.displaying.Event;
import edu.arizona.sista.learn.model.displaying.WatchingSomething;
import edu.arizona.sista.learn.model.inferences.Predicate;
import edu.arizona.sista.learn.model.interest.Head;
import edu.arizona.sista.learn.model.interest.WalkingBackwards;
import edu.arizona.sista.learn.model.interest.WalkingForwards;
import edu.arizona.sista.learn.util.Utils;
import edu.arizona.sista.learn.util.Utils.ActorName;
import edu.arizona.sista.learn.util.Utils.MovieName;

public class Scene {
	
	public MovieName _name;
	public int _frames;
	public List<SpatialThing> _spatialThings;
	public List<Person> _people;
	public List<Event> _events;
	public List<Predicate> _predicates; //static predicates
	
	public Scene(MovieName name) {
		_name = name;
		_spatialThings = new ArrayList<SpatialThing>();
		_people = new ArrayList<Person>();
		_events = new ArrayList<Event>();
		_predicates = new ArrayList<Predicate>();
	}
	
	public List<SpatialThing> getSpatialThings() {
		return _spatialThings;
	}
	
	/**
	 * Load position data from file.
	 * @param dataFile position data file.
	 */
	public void loadPositionData(File dataFile) {
		try {
			// Create BufferedReader to read csv file
			BufferedReader br = new BufferedReader(new FileReader(dataFile));
			SpatialThing spatialThing = null;
			String strLine = "";
			int prevId = -1, id = 0;
			String label = null;
			int prevFrame = -1, frame = 0;
			
			// Skip first line
			strLine = br.readLine();

			// Read comma separated file line by line
			while ((strLine = br.readLine()) != null) {
				//0 id
				//1 xmin
				//2 ymin
				//3 xmax
				//4 ymax
				//5 frame
				//6 lost
				//7 occluded
				//8 generated
				//9 label
				
				String[] tokens = strLine.split(",");
				if (tokens.length > 1) {
					id = Integer.parseInt(tokens[0].trim());
					int xmin = Integer.parseInt(tokens[1]);
					int ymin = Integer.parseInt(tokens[2]);
					int xmax = Integer.parseInt(tokens[3]);
					int ymax = Integer.parseInt(tokens[4]);
					label = tokens[9].trim();
					frame = Integer.parseInt(tokens[5].trim());
					
					//if id has changed then create a new SpatialThing
					if (prevId != id){
						//add the last SpatialThing to the list
						if (prevId != -1) _spatialThings.add(spatialThing);

						//create a new SpatialThing
						if (label.toLowerCase().contains("hand")) spatialThing = new Hand(id, label);
						else if (label.toLowerCase().contains("object")) spatialThing = new Object(id, label);
						else if (label.toLowerCase().contains("head")) spatialThing = new Head(id, label);
						else spatialThing = new SpatialThing(id, label);
					}
					else if (prevFrame != -1 && frame != prevFrame+1)
						System.err.println("Missing frame(s) between " + prevFrame + " " + frame);
					
					//coordinates are at the center of the SpatialThing
					float x = xmin + (xmax - xmin) / 2;
					float y = ymin + (ymax - ymin) / 2;
					spatialThing.addCoordinate(new float[]{x, y});
					
					int dimx = Math.abs(xmax - xmin);
					int dimy = Math.abs(ymax - ymin);
					spatialThing.addDimension(new int[]{dimx, dimy});
					
				}
				prevId = id;
				prevFrame = frame;
			}
			
			//add the very last SpatialThing to the list
			_spatialThings.add(spatialThing);
			
			//number of frames
			_frames = frame;
		} catch (Exception e) {
			System.out.println("Exception while reading csv file: " + e);
		}
	}
	
	public List<Hand> getHands(){
		List<Hand> hands = new ArrayList<Hand>();
		for(SpatialThing st : _spatialThings) if (st instanceof Hand) hands.add((Hand) st);
		return hands;
	}

	public List<Object> getObjects() {
		List<Object> objects = new ArrayList<Object>();
		for(SpatialThing st : _spatialThings) if (st instanceof Object) objects.add((Object) st);
		return objects;
	}

	public Person getActorFromBodyPart(SpatialThing part) {
		for (Person p : _people)
			for (SpatialThing s : p._bodyParts) if(part.equals(s)) return p;
		return null;
	}

	/**
	 * Load body parts from file and assign them to a person.
	 * @param dataFile position data file.
	 */
	public void loadBodyParts(File dataFile) {
		try {
			Scanner bodyPartsScanner = new Scanner(dataFile);

			//skip first line
			bodyPartsScanner.nextLine();
			
			String partID = null;
			
			while (bodyPartsScanner.hasNextLine()) {
			    String line = bodyPartsScanner.nextLine();

			    //0 bodyPartId
			    //1 bodyPartLabel
			    //2 partID
			    //3 personLabel
			
			    // Break comma separated line using ","
				String[] tokens = line.split(",");
			    
				int bodyPartId = Integer.parseInt(tokens[0]);
				partID = tokens[2];

				//one "body part" actually corresponds to the person: replace it
				String bodyPartLabel = tokens[1];
				if (bodyPartLabel.toLowerCase().contains("person"))	processPerson(bodyPartId, partID);

				//connect other kinds of body parts
				else insert(partID, bodyPartId);
			}
			bodyPartsScanner.close();
		} catch (Exception e) {
			System.out.println("Exception while reading csv file: " + e);
			e.printStackTrace();
		}
	}

	/**
	 * Connect spatial things labeled "Person" to people
	 * @param bodyPartId
	 * @param partID
	 */
	private void processPerson(int bodyPartId, String partID) {
		Person belle = null;
		
		//First check if the person is listed
		boolean listed = false;
		for(int i = 0; i < _people.size() && !listed; i++){
			belle = _people.get(i);
			if (belle._partID.equals(partID)) listed = true;
		}
		if (!listed){
			belle = new Person(Utils.getActorNameFromPartID(_name, partID), partID);
			_people.add(belle);
		}
		
		//Get the corresponding SpatialThing
		int i = 0;
		SpatialThing st = null;
		while(i < _spatialThings.size()){
			st = _spatialThings.get(i);
			if (st._id == bodyPartId) break;
			i++;
		}
		
		//Transfer attributes to belle
		belle.setCoordinates(st.getCoordinates());
		belle.setId(st._id);
		
		//Remove belle from spatial things
		_spatialThings.remove(i);
	}

	/**
	 * Insert body part into the list of body parts of a person
	 * @param partID id of the person
	 * @param bodyPartId id of the body part
	 */
	private void insert(String partID, int bodyPartId) {
		Person belle = null;
		
		//First check if the person is listed
		boolean listed = false;
		for(int i = 0; i < _people.size() && !listed; i++){
			belle = _people.get(i);
			if (belle._partID.equals(partID)) listed = true;
		}
		if (!listed){
			belle = new Person(Utils.getActorNameFromPartID(_name, partID), partID);
			_people.add(belle);
		}

		//add body Part
		belle.addBodyPart(getSpatialThing(bodyPartId));
	}

	/**
	 * Get spatial thing from id
	 * @param bodyPartId id of the spatial thing
	 */
	public SpatialThing getSpatialThing(int id){
		for (SpatialThing output : _spatialThings) if (output._id == id) return output;
		return null;
	}

	public void addEvent(Event e) {
		_events.add(e);
		if (e instanceof MovementEvent){
			List<RectilinearTrajectory> list = getRectilinearTrajectories();
			for (RectilinearTrajectory l: list){
				if (((MovementEvent) e)._doer.equals(l._doer) &&
						e._time.temporallySubsumes(l._time, Interval.NOISE_WINDOW))
					((MovementEvent) e).addSubEvent(l);
			}
		}
	}
	
	public String toString() {
		StringBuffer sb = new StringBuffer();
		
		//Spatial things
		for (SpatialThing st : _spatialThings)
			if (st instanceof Hand || st instanceof Object)
				sb.append(st);
		
		//People
		for (Person p : _people) sb.append(p);

		//Events
		for (Event e : _events) sb.append(e);
		
		//Static predicates
		for (Predicate p : _predicates) sb.append(p + "\n");
		
		return sb.toString();
	}

	public Person getPerson(String partID) {
		for (Person p : _people) if (p._partID.equals(partID) || p._label.equals(partID)) return p;
		return null;
	}

	public Person getPerson(ActorName actorName) {
		for (Person p : _people) if (p._label.equals(actorName.toString())) return p;
		return null;
	}

	public List<HoldingAnObject> getHoldingAnObjectEvents() {
		List<HoldingAnObject> output = new ArrayList<HoldingAnObject>();
		for(Event e : _events) if (e instanceof HoldingAnObject) output.add((HoldingAnObject) e);
		return output;
	}

	public List<WatchingSomething> getWatchingSomethingEvents() {
		List<WatchingSomething> output = new ArrayList<WatchingSomething>();
		for(Event e : _events) if (e instanceof WatchingSomething) output.add((WatchingSomething) e);
		return output;
	}

	public void addPredicate(Predicate predicate) {
		_predicates.add(predicate);		
	}

	public List<WalkingBackwards> getWalkingBackwardsEvents() {
		List<WalkingBackwards> output = new ArrayList<WalkingBackwards>();
		for(Event e : _events) if (e instanceof WalkingBackwards) output.add((WalkingBackwards) e);
		return output;
	}

	public List<WalkingForwards> getWalkingForwardsEvents() {
		List<WalkingForwards> output = new ArrayList<WalkingForwards>();
		for(Event e : _events) if (e instanceof WalkingForwards) output.add((WalkingForwards) e);
		return output;
	}
	
	public List<DisplayingSomething> getDisplayingSomethingEvents() {
		List<DisplayingSomething> output = new ArrayList<DisplayingSomething>();
		for(Event e : _events) if (e instanceof DisplayingSomething) output.add((DisplayingSomething) e);
		return output;
	}

	public static MovieName getName(File file) {
		try{
			if (file.getAbsolutePath().toLowerCase().contains("map")) return MovieName.Map;
			if (file.getAbsolutePath().toLowerCase().contains("newspaper")) return MovieName.Newspaper;
			if (file.getAbsolutePath().toLowerCase().contains("y106")) return MovieName.Y106;
			else return MovieName.Y201;
		} catch(NullPointerException e){ e.printStackTrace(); }
		return null;
	}

	public void loadTracks(File trackFile) {
		try {
			Scanner trackScanner = new Scanner(trackFile);
			int lastId = -1;
			Person p = null;
			
			//skip first line
			trackScanner.nextLine();
			
			while (trackScanner.hasNextLine()) {
			    String line = trackScanner.nextLine();

			    //0 actorId
			    //1 actorLabel
			    //2 frame
			    //3 x
			    //4 y
			
			    // Break comma separated line using ","
				String[] tokens = line.split(",");
			    
				int id = Integer.parseInt(tokens[0]);
				String actorLabel = tokens[1];
				int frame = Integer.parseInt(tokens[2]);
				double x = Double.parseDouble(tokens[3]);
				double y = Double.parseDouble(tokens[4]);

				if (id != lastId){
					//new person
					p = getPerson(id);
					if (p == null) p = getPerson(actorLabel);
					if (p == null){
						p = new Person(id, actorLabel);
						_people.add(p);
					}
				}
				double[] pos = new double[]{x, y};
				p.addTrack(frame, pos);
				lastId = id;			
			}
			trackScanner.close();
		} catch (Exception e) {
			System.out.println("Exception while reading csv file: " + e);
			e.printStackTrace();
		}		
	}

	public Person getPerson(int id) {
		for(Person p: _people) if (p._id == id) return p;
		return null;
	}

	public List<RectilinearTrajectory> getRectilinearTrajectories() {
		List<RectilinearTrajectory> output = new ArrayList<RectilinearTrajectory>();
		for(Event e : _events) if (e instanceof RectilinearTrajectory) output.add((RectilinearTrajectory) e);
		return output;
	}

	public List<MovementEvent> getMovementEvents() {
		List<MovementEvent> output = new ArrayList<MovementEvent>();
		for(Event e : _events) if (e instanceof MovementEvent) output.add((MovementEvent) e);
		return output;
	}

}